# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>

=head1 NAME

Testopia::Table - Produces display tables for Testopia lists

=head1 DESCRIPTION

A table is generated as a result of a query. This module returns 
a list of Testopia objects that were queried for. It supports 
pagination of data as well as sorting. It takes the following 
arguments:

=over 

=item type - one of 'case', 'plan', 'run', 'caserun', or 'environment'

=item url - the cgi file that is calling this 

=item cgi - a CGI object

=item list - A reference to a list

=item query - An SQL query string, usually generated by Search.pm

=back

=head1 SYNOPSIS

 $table = Testopia::Table->new($type, $url, $cgi, $list, $query);

=cut

package Testopia::Table;

use strict;

use Bugzilla;
use Bugzilla::Util qw(!list);
use Bugzilla::Error;
use Testopia::Util;
use Testopia::TestCase;
use Testopia::TestPlan;
use Testopia::TestRun;
use Testopia::TestCaseRun;

###############################
####    Initialization     ####
###############################

# For use in sorting functions which do not allow arguments
our $field;
our $reverse;

=head1 METHODS

=head2 new

Instantiates a new table object

=cut

sub new {
    my $invocant = shift;
    my $class = ref($invocant) || $invocant;
  
    my $self = {};
    bless($self, $class);

    $self->init(@_);
 
    return $self;
}

=head2 init

Private constructor for this class

=cut

sub init {
    my $self = shift;
    my ($type, $url, $cgi, $list, $query)  = @_;
    $self->{'user'} = Bugzilla->user;
    $self->{'type'} = $type ||   ThrowCodeError('bad_arg',
                                   {argument => 'type',
                                    function => 'Testopia::Table::_init'});
    $self->{'url_loc'} = $url;
    $self->{'cgi'} = $cgi;
    my $debug = $cgi->param('debug') if $cgi;
    my @list;
    if ($query){
        # For paging we need to know the total number of items
        # but Search.pm returns a query with a subset
        my $countquery = $query;
        $countquery =~ s/ LIMIT.*$//;

        my $dbh = Bugzilla->switch_to_shadow_db();

        my $count_res = $dbh->selectcol_arrayref($countquery);
        my $count = scalar @$count_res;
        print "<p> Total rows: $count</p>" if $debug;

        $self->{'list_count'} = $count;
        my @ids;
        my $list = $dbh->selectcol_arrayref($query);

        $dbh = Bugzilla->switch_to_main_db();

        foreach my $id (@$list){
            my $o;
            if ($type eq 'case'){
                $o = Testopia::TestCase->new($id);
            }
            elsif ($type eq 'plan'){
                $o = Testopia::TestPlan->new($id);
            }
            elsif ($type eq 'run'){
                $o = Testopia::TestRun->new($id);
            }
            elsif ($type eq 'case_run'){
                $o = Testopia::TestCaseRun->new($id);
            }
            elsif ($type eq 'environment'){
                $o = Testopia::Environment->new($id);
            }
            push (@ids, $id);
            push (@list, $o);
        }
        $self->{'list'} = \@list;
        $self->{'view_count'} = scalar @list; 
        $self->{'id_list'} = join(",", @$count_res);
    }
    if ($cgi){
        $self->{'viewall'} = $cgi->param('viewall');
        $self->{'page'} = $cgi->param('page') || 0;
    }

    print $query if $debug;
    exit if $debug;
#    elsif (!$query && !$list){
#        my @list;
#        foreach my $id (split(",", $self->get_saved_list())){
#            if ($self->{'type'} eq 'case'){
#                my $o = Testopia::TestCase->new($id);
#                push @list, $o;
#                $o->category;
#                $o->status;
#                $o->priority;
#            }
#            elsif ($self->{'type'} eq 'plan'){
#                my $o = Testopia::TestPlan->new($id);
#                push @list, $o;
#                $o->test_case_count;
#                $o->test_run_count;
#            }
#            elsif ($self->{'type'} eq 'run'){
#                my $o = Testopia::TestCase->new($id);
#                push @list, $o;
##                $o->category;
##                $o->status;
##                $o->priority;                
#            }
#            elsif ($self->{'type'} eq 'caserun'){
#                my $o = Testopia::TestCase->new($id);
#                push @list, $o;
##                $o->category;
##                $o->status;
##                $o->priority;
#            }
#            elsif ($self->{'type'} eq 'attachment'){
#                my $o = Testopia::TestCase->new($id);
#                push @list, $o;
##                $o->category;
##                $o->status;
##                $o->priority;
#            }
#            else {
#                ThrowUserError('unknown-type');
#            }
#        }
#        $self->{'list'} = \@list;
#    }
#    else {
#        $self->{'list'} = $list;
#    }
#    my @params = split(":", $cgi->cookie('TESTORDER'));
#    $self->{'last_sort'} = shift @params || undef;
#    $self->{'reverse_sort'} = shift @params || undef;
#
#    #### SORT ####
#    # This is very inefficient. It would be much better to have 
#    # the database do this.
#    my $order = $cgi->param('order');
#    if ($order){
#        $self->sort_fields($order);
#        $self->{'reverse_sort'} = ($self->{'reverse_sort'} ? 1 : 0);
#    }
#    
    #### SAVE ####
    # Save the list of testcases for use in paginating and sorting
    $self->save_list;
#
#    #### SPLICE ####
#    # If we are using a paged view of the data we split it up here 
#    $self->get_page($self->{'page'});
    
}

###############################
####       Methods         ####
###############################

=head2 save_list

Saves the last list to the database as a hidden saved search
Used only in list context

=cut

sub save_list {
    my $self = shift;
    return if ($self->{'user'}->id == 0);
#    my @ids;
#    foreach my $i (@{$self->{'list'}}){
#            push @ids, $i->id;
#    } 
#    my $list = join(",", @ids);
    my $dbh = Bugzilla->dbh;
    if ($self->{'id_list'}){
        $dbh->bz_start_transaction();
        my ($is) = $dbh->selectrow_array(
                "SELECT 1 FROM test_named_queries 
                 WHERE userid = ? AND name = ?", undef,
                 ($self->{'user'}->id, "__". $self->{'type'} ."__"));
    
        if ($is) {
            $dbh->do("UPDATE test_named_queries SET query = ?
                      WHERE userid = ? AND name = ?", undef,
                      (join(",", $self->{'id_list'}), $self->{'user'}->id, "__". $self->{'type'} ."__"));
        }
        else {
            $dbh->do("INSERT INTO test_named_queries (userid, name, isvisible, query) VALUES(?,?,?,?)", undef,
                      ($self->{'user'}->id, "__". $self->{'type'} ."__", 0, join(",", $self->{'id_list'})));
        }
        $dbh->bz_commit_transaction();
    }
#    $self->{'list_count'} = scalar @ids unless $self->{'query'};
}

=head2 get_saved_list

Retrieves a saved list from the database
Used only in list context

=cut

sub get_saved_list {
    my $self = shift;
    return undef if ($self->{'user'}->id == 0);
    my $type = shift || $self->{'type'};
    my $dbh = Bugzilla->dbh;
    my ($list) = $dbh->selectrow_array(
            "SELECT query FROM test_named_queries 
             WHERE userid = ? AND name = ?", undef,
             ($self->{'user'}->id, "__". $type ."__"));
    my @list = split(',', $list);
    return \@list;
}

# used by the sort function to check which field to sort on
# we turn off warnings to supress the nonnumeric vs numeric junk
sub sort_fields {
    no warnings;
    if ($field eq 'category'){
        if ($reverse){
            $a->category->name cmp $b->category->name;
        }
        else {    
            $b->category->name cmp $a->category->name; 
        }
    }
    elsif ($field eq 'plans'){
        if ($reverse){
            scalar @{$a->plans} <=> scalar @{$b->plans};
        }
        else {    
            scalar @{$b->plans} <=> scalar @{$a->plans};
        }
    }
    else{
        if ($reverse){
            $a->{$field} cmp $b->{$field};
        }
        else {    
            $b->{$field} cmp $a->{$field}; 
        }
    }
    use warnings;
}

sub sort_list {
    my $self = shift;
    $field = shift;
    $reverse = $self->{'reverse_sort'};
    my @list = sort sort_fields @{$self->{'list'}};
    $self->{'list'} = \@list; 
    return $self->{'list'};
}

sub get_page {
    my $self = shift; 
    if ($self->{'viewall'}){
        return $self->{'list'};
    }
    my $pagenum = shift || $self->{'page'};
    $self->{'page'} = $pagenum;
    my $offset = $pagenum * $self->page_size;
    my @list = @{$self->{'list'}};
    @list = splice(@list, $offset, $self->page_size);
    $self->{'list'} = \@list;
    return $self->{'list'};
}

sub get_next{
    my $self = shift; 
    my ($curr) = @_;
    
    my $list = $self->get_list;
    my $ref = lsearch($curr, $list);
    return undef if $ref == -1;
    return $list->[$ref];
}
###############################
####      Accessors        ####
###############################

sub list       { return $_[0]->{'list'};       }
sub id_list    { return $_[0]->{'id_list'};       }
sub list_count { return $_[0]->{'list_count'}; }
sub view_count { return $_[0]->{'view_count'}; }
sub page       { return $_[0]->{'page'}; }
sub url_loc    { return $_[0]->{'url_loc'}; }
sub type       { return $_[0]->{'type'}; }

=head2 page_size

Returns an ineger representing how many items should appear on a page

=cut

sub page_size    {
    my $self = shift;
    my $cgi = $self->{'cgi'};
    my $size = $cgi->param('pagesize') || 25;
    return $size;
}

=head2 get_order_url

Returns a URL query string from a CGI object which is used by 
column headers to produce a sort order

=cut

sub get_order_url {
    my $self = shift;
    return $self->get_url('page','order');
}

=head2 get_page_url

Retrns a URL query string from a CGI object which is used by
the page navigation links to move from page to page.

=cut

sub get_page_url {
    my $self = shift;
    my $cgi = $self->{'cgi'};
    return $self->{'url_loc'} ."?". $cgi->canonicalise_query('page', 'pagesize', 'viewall');
}

sub get_url {
    my ($self, @drops) = @_;
    my $cgi = $self->{'cgi'};
    $self->{'url'} = $self->{'url_loc'} ."?". $cgi->canonicalise_query(@drops);
    return $self->{'url'};
}

sub get_query_part {
    my $self = shift;
    my $cgi = $self->{'cgi'};
    my @keys = $cgi->param;
    my $qstring;
    foreach my $key (@keys){
        my @vals = $cgi->param($key);
        foreach my $val (@vals){
            $qstring .= $key ."=". url_quote($val) ."&";
        }
    }
    chop $qstring;
    return $qstring
}

=head2 page_count

Returns a total count of the number of pages returned by a query.
Determined in part by page_size

=cut

sub page_count {
    my $self = shift;
    if ($self->list_count % $self->page_size){
        use integer;
        return ($self->list_count / $self->page_size) + 1;
    }
    return $self->list_count/$self->page_size;
}

sub reverse_sort {
    my $self = shift;
    return $self->{'reverse_sort'};
}

sub ajax {
    my $self = shift;
    return $self->{'ajax'} ? 1 : 0;
}

sub arrow {
    my $self = shift;
    if ($self->{'reverse_sort'}) {
        $self->{'arrow'} = '<img src="images/arrow_desc.png" border="0">';
    }
    else {
        $self->{'arrow'} = '<img src="images/arrow_asc.png" border="0">';
    }
    return $self->{'arrow'};
}

sub to_yui_json {
    my $self = shift;
    my $out = '';
    $out .= '{"ResultSet":{';
    $out .= '"totalResultsAvailable":' . $self->list_count .',';
    $out .= '"totalResultsReturned":'  . $self->page_size .',';
    $out .= '"firstResultPosition":' . $self->page * $self->page_size .',';
    $out .= '"Result":[';
    foreach my $i (@{$self->list}){
        $out .= '{';
        $out .= '"ID":"' .      $i->id . '",';
        $out .= '"Name":"' .    $i->name . '",';
        $out .= '"Author":"' .  $i->author->login . '",';
        $out .= '"Created":"' . $i->creation_date . '",';
        $out .= '"Product":"' . $i->product->name . '",';
        $out .= '"Version":"' . $i->product_version . '",';
        $out .= '"Type":"' .    $i->type . '",';
        $out .= '"Cases":"' .   $i->test_case_count . '",';
        $out .= '"Runs":"' .    $i->test_run_count . '",';
        $out .= '"ClickUrl":"tr_show_plan.cgi?plan_id=' . $i->id;
        $out .= '"},';
    }
    chomp($out);
    $out .= ']}}';
    return $out;
        
}

sub to_ext_json {
    my $self = shift;
    
    my $out = '';
    $out .= '{';
    $out .= '"totalResultsAvailable":' . $self->list_count .',';
    $out .= '"Result":[';
    foreach my $i (@{$self->list}){
        $out .= $i->TO_JSON . ',' if $i;
    }
    chop($out) if scalar @{$self->list};
    $out .= ']}';
    return $out;
        
}

=head1 SEE ALSO

Testopia::Search

=head1 AUTHOR

Greg Hendricks <ghendricks@novell.com>

=cut

1;
